<?php

namespace W3\Element;

use W3\Element;
use W3\Token;
use W3\Request;
use W3\Validate;
use W3\Cookie;

/**
 * Form 帮手类
 *
 * @author edikud
 * @date 2022/10/22
 * @copyright Copyright (c) 2022 W3 (http://www.mcooo.com)
 * @license GNU General Public License 2.0
 */
class Form extends Element 
{
    /**
     * 验证类
     *
     * @access private
     */
    protected $validate;
	
    /**
     * 输入元素列表
     *
     * @access private
     * @var array
     */
    private $inputs = [];
	
    /**
     * @var boolean
     */
    private $enabled;
	
    /**
     * 表单消息
     *
     * @access protected
     * @var string
     */
    protected $message;	
	
    public function __construct(?string $action = null, ?string $class = null, bool $csrf = true) 
	{
		
		$this->validate = Validate::make()->setBreak();
		
		$this
		   ->close(false)
		   ->tag('form')
		   ->addClass($class ?? 'w3-form');
		
        /** 设置表单属性 */
        $this->action($action, $csrf);
        $this->method('post');
        $this->encodeType('application/x-www-form-urlencoded');
    }
	
    /**
     * 设置提示信息
     *
     * @access public
     * @param string $message 提示信息
     * @return Form_Element
     */
    public function message($message)
    {
        if (empty($this->message)) {
            $this->message = Div::make(NULL, 'form-message')->wrap(Div::make(NULL, 'form-group'));
            $this->set($this->message);
        }

        $this->message->reset()->set($message);
        return $this;
    }
	
    /**
     * 设置表单编码方案
     *
     * @access public
     * @param string $enctype 编码方法
     * @return Form
     */
    public function encodeType(string $enctype): Form
    {
        return $this->attribute('enctype', $enctype);
    }

    /**
     * 增加输入元素
     *
     * @access public
     * @param Form $input 输入元素
     * @param string $after 在某个元素之后
     * @return Form
     */
    public function addInput(Group $input, ?string $after = NULL): Form
    {
		$input->container = Div::make(NULL, 'form-group');
		if('hidden' == $input->getAttribute('type')) {
		    $input->container->hide();
		}
		$this->inputs[$input->name] = $input;
		$this->set($input, $input->name, $after);
        return $this;
    }

    /**
     * 获取输入项
     *
     * @access public
     * @param string $name 输入项名称
     * @return mixed
     */
    public function getInput(string $name)
    {
        return $this->inputs[$name];
    }

    /**
     * 获取所有输入项的提交值
     *
     * @access public
     * @return array
     */
    public function getAllRequest(): array
    {
        $result = [];
        $source = ('post' == $this->getAttribute('method')) ? $_POST : $_GET;
        foreach ($this->inputs as $name => $input) 
		{
            $result[$name] = $source[$name] ?? NULL;
        }
        return $result;
    }

    /**
     * 设置表单提交方法
     *
     * @access public
     * @param string $method 表单提交方法
     * @return Form
     */
    public function method(?string $method): Form
    {
        return $this->attribute('method', $method);
    }

    /**
     * @return static
     */
    public function novalidate(): Form
    {
        return $this->attribute('novalidate');
    }

    /**
     * @return this
     */
    public function acceptsFiles(): Form
    {
        return $this->attribute('enctype', 'multipart/form-data');
    }

    /**
     * 设置表单提交目的
     *
     * @access public
     * @param string $action 表单提交目的
     * @return Form
     */
    public function action(?string $action = null, bool $csrf = true): Form
    {
		$this->enabled = $csrf;
        return $this->attribute('action', $csrf ? Token::buildUrl($action ?? Request::instance()->url(true)) : $action);
    }

    /**
     * 获取此表单的所有输入项固有值
     *
     * @access public
     * @return array
     */
    public function values(): array
    {
        $values = [];
        foreach ($this->inputs as $name => $input) 
		{
            $values[$name] = $input->value;
        }
        return $values;
    }

    /**
     * 获取此表单的所有输入项
     *
     * @access public
     * @return array
     */
    public function getInputs(): array
    {
        return $this->inputs;
    }

    /**
     * 获取提交数据源
     *
     * @access public
     * @param array $params 数据参数集
     * @return array
     */
    public function getParams(array $params): array
    {
        $result = [];
        $source = ('post' == $this->getAttribute('method')) ? $_POST : $_GET;
        foreach ($params as $param) 
		{
            $result[$param] = $source[$param] ?? NULL;
        }

        return $result;
    }

    /**
     * 获取提交数据源
     *
     * @access public
     * @param array $params 数据参数
     * @return array
     */
    public function getParam(string $name)
    {
        $source = ('post' == $this->getAttribute('method')) ? $_POST : $_GET;
        return $source[$name] ?? NULL;
    }

    /**
     * 注册验证器
     *
     * @return Form
     */
    public function register(string $method, callable $callable): Form
    {
        $this->validate->register($method, $callable);
        return $this;
    }
	
    /**
     * 验证表单
     *
     * @return array
     */
    public function validate(?string $url = null): array
    {
		$rules = [];
		$this->enabled && $rules['_'] = [[function() {

			return Request::instance()->get('_') == Token::get(Request::instance()->referer());
			
		}, __("令牌已过期，请求无效.")]];
		
        foreach ($this->inputs as $name => $input) 
		{
            $rules[$name] = $input->rules;
        }

        $id = md5(implode('"', array_keys($this->inputs)));

        /** 表单值 */
        $formData = $this->getParams(array_keys($rules));
		
		$this->enabled && $formData['_'] = Request::instance()->get('_');
		
        $error = $this->validate->run($formData, $rules);

        if ($error) {
            /** 利用Cookie记录错误 */
            Cookie::set('__w3_form_message_' . $id, json_encode($error));

            /** 利用Cookie记录表单值 */
            Cookie::set('__w3_form_record_' . $id, json_encode($formData));
        }

        return $error;
    }
	
    /**
     * 显示表单
     *
     * @return void
     */
    public function render()
    {
        $id = md5(implode('"', array_keys($this->inputs)));
        $record = Cookie::get('__w3_form_record_' . $id);
        $message = Cookie::get('__w3_form_message_' . $id);

        /** 恢复表单值 */
        if (!empty($record)) {
            $record = json_decode($record, true);
            $message = json_decode($message, true);
			
			isset($message['_']) && $this->message($message['_']);

            foreach ($this->inputs as $name => $input) 
			{
                $input->value($record[$name] ?? $input->value);

                /** 显示错误消息 */
                if (isset($message[$name])) {
                    $input->message($message[$name]);
                }
            }

            Cookie::delete('__w3_form_record_' . $id);
        }

        Cookie::delete('__w3_form_message_' . $id);
		
		return parent::render();
    }
}